home *** CD-ROM | disk | FTP | other *** search
/ Nebula 1 / Nebula One.iso / Communications / PPPMonitor1.16 / Source / ExecRunCommand.m < prev    next >
Text File  |  1996-01-29  |  11KB  |  445 lines

  1. // -------------------------------------------------------------------------------------
  2. // ExecRunCommand.m
  3. // (Indent:4, Tabs:4)
  4. // -------------------------------------------------------------------------------------
  5. // Copyright 1996 Persistent Technologies, Inc. - all rights reserved
  6. // -------------------------------------------------------------------------------------
  7. // This source code comes with no warranty of any kind, and the user assumes all 
  8. // responsibility for its use.
  9. // -------------------------------------------------------------------------------------
  10. #import <appkit/appkit.h>
  11. #import <libc.h>
  12. #import <mach/cthreads.h>
  13. #import <stdlib.h>
  14. #import <stdarg.h>
  15. #import <string.h>
  16. #import <pwd.h>
  17. #import <sys/types.h>
  18. #import <sys/wait.h>
  19. #import "ExecRunCommand.h"
  20.  
  21. // -------------------------------------------------------------------------------------
  22. // pending pid list - keep track of child process creation and termination
  23.  
  24. /* pid list */
  25. #define MAX_PIDLIST_SIZE    64    /* maximum concurrent child processes */
  26. typedef struct {
  27.     int            pid;
  28.     int            didExit;
  29.     union wait    status;
  30. } pidStatus_t;
  31. static pidStatus_t pidList[MAX_PIDLIST_SIZE] = { 0 };
  32. static int pidCount = 0;
  33.  
  34. /* return pidList index of specified pid number */
  35. static int _pid_index(int pid)
  36. {
  37.     if (pid > 0) {
  38.         int n;
  39.         for (n = 0; n < pidCount; n++) { if (pid == pidList[n].pid) return n; }
  40.     }
  41.     return -1; // not found
  42. }
  43.  
  44. /* add pid to list (does not check to see if it already exists!) */
  45. static int _pid_add(int thePid)
  46. {
  47.     int ndx;
  48.     for (ndx = 0; (ndx < pidCount) && pidList[ndx].pid; ndx++);
  49.     pidList[ndx].pid = thePid;
  50.     pidList[ndx].didExit = NO;
  51.     if (ndx == pidCount) pidCount++; // upper bounds (MAX_PIDLIST_SIZE) not checked
  52.     return ndx;
  53. }
  54.  
  55. /* wait for pid to exit (keep track of all pid exit reports) */
  56. static BOOL _pid_wait(int thePid, union wait *rtnStat)
  57. {
  58.     int pidNdx = _pid_index(thePid);
  59.     if (pidNdx < 0) pidNdx = _pid_add(thePid); //(ERROR) NOT FOUND - add anyway
  60.     
  61.     /* wait for pid to exit */
  62.     if (!(pidList[pidNdx].didExit)) {
  63.         for (;;) {
  64.             union wait status;
  65.             int pid = wait(&status);
  66.             if (pid == -1) { pidNdx = 0; break; };
  67.             pidNdx = _pid_index(pid);
  68.             if (pidNdx < 0) pidNdx = _pid_add(pid); //(ERROR) NOT FOUND - add anyway
  69.             pidList[pidNdx].didExit = YES;
  70.             pidList[pidNdx].status = status;
  71.             if (thePid == pid) break;
  72.         }
  73.     }
  74.     
  75.     /* return reported status */
  76.     if (pidNdx >= 0) {
  77.         *rtnStat = pidList[pidNdx].status;
  78.         pidList[pidNdx].pid = 0;
  79.         return YES;
  80.     }
  81.     return NO;
  82.  
  83. }
  84.  
  85. // -------------------------------------------------------------------------------------
  86. // ExecRunCommand private methods
  87. @interface ExecRunCommand(Private)
  88. + (struct passwd*)_getpwnam:(const char*)user;
  89. + (BOOL)_isRoot;
  90. + (int)_setenv:(char**)ep :(char*)eVal :(char*)fmt, ...;
  91. - (BOOL)_runCommand:(const char*)command user:(const char*)user;
  92. - (void)_sendSignal:(int)sigval;
  93. - (void)_stopCommand;
  94. - (int)_popen:(const char*)cmd user:(const char*)user;
  95. - (int)_pclose;
  96. - (void)_gotData;
  97. static void gotData(int fd, void *self);
  98. - (void)_commandOutput:(const char*)buf len:(int)len;
  99. @end
  100.  
  101. // --------------------------------------------------------------------------------
  102. @implementation ExecRunCommand 
  103.  
  104. // --------------------------------------------------------------------------------
  105.  
  106. /* initialize */
  107. - init
  108. {
  109.     [super init];
  110.     cmdChild = 0;
  111.     inputDescriptor = 0;
  112.     delegate = nil;
  113.     tag = 0;
  114.     return self;
  115. }
  116.  
  117. /* free */
  118. - _free:sender { return [self free]; }
  119. - free
  120. {
  121.     if (cmdChild > 0) {
  122.         fprintf(stderr,"cannot free yet, killing command ...\n");
  123.         [self killCommand];
  124.         [self perform:@selector(_free:) with:self afterDelay:500 cancelPrevious:YES];
  125.         return (id)nil;
  126.     }
  127.     [super free];
  128.     return (id)nil;
  129. }
  130.  
  131. // --------------------------------------------------------------------------------
  132.  
  133. /* run command */
  134. + runCommand:(const char*)command user:(const char*)user output:(id)theDelegate
  135. {
  136.     BOOL didStart;
  137.     ExecRunCommand *mySelf = [[self alloc] init];
  138.     [mySelf setDelegate:theDelegate];
  139.     didStart = [mySelf _runCommand:command user:user];
  140.     if (!didStart) { [mySelf free]; mySelf = (id)nil; }
  141.     return mySelf;
  142. }
  143.  
  144. /* run command */
  145. + runCommand:(const char*)command output:(id)theDelegate
  146. {
  147.     return [ExecRunCommand runCommand:command user:(char*)nil output:theDelegate];
  148. }
  149.  
  150. /* similar to "system(...)" */
  151. + (int)system:(const char*)command user:(const char*)user output:(id)theDelegate
  152. {
  153.     int err, infd;
  154.     ExecRunCommand *mySelf = [[self alloc] init];
  155.     [mySelf setDelegate:theDelegate];
  156.     infd = [mySelf _popen:command user:user];
  157.     if (infd >= 0) {
  158.         int cnt;
  159.         char buffer[1025];
  160.         do {
  161.             cnt = read(infd, buffer, sizeof(buffer));
  162.             if (cnt != -1) [mySelf _commandOutput:buffer len:cnt];
  163.         } while (cnt > 0);
  164.         err = [mySelf _pclose];
  165.         close(infd);
  166.     } else err = RUNCMD_EXEC; // could not execute
  167.     mySelf->cmdChild = 0;
  168.     [mySelf free];
  169.     return err;
  170. }
  171.  
  172. /* see "system:user:output:" */
  173. + (int)system:(const char*)command user:(const char*)user
  174. {
  175.     return [self system:command user:user output:nil];
  176. }
  177.  
  178. /* see "system:user:output:" */
  179. + (int)system:(const char*)command
  180. {
  181.     return [self system:command user:(char*)nil output:nil];
  182. }
  183.  
  184. /* set tag */
  185. - setTag:(int)theTag
  186. {
  187.     tag = theTag;
  188.     return self;
  189. }
  190.  
  191. /* return tag */
  192. - (int)tag
  193. {
  194.     return tag;
  195. }
  196.  
  197. /* return true if command is still active */
  198. - (BOOL)isActive
  199. {
  200.     return (cmdChild > 0)? YES : NO;
  201. }
  202.  
  203. /* set delegate */
  204. - setDelegate:(id)theDelegate
  205. {
  206.     delegate = theDelegate;
  207.     return self;
  208. }
  209.  
  210. /* return delegate */
  211. - (id)delegate
  212. {
  213.     return delegate;
  214. }
  215.  
  216. /* interrupt command */
  217. - interruptCommand
  218. {
  219.     [self _sendSignal:SIGINT];
  220.     return self;
  221. }
  222.  
  223. /* terminate command */
  224. - terminateCommand
  225. {
  226.     [self _sendSignal:SIGTERM];
  227.     return self;
  228. }
  229.   
  230. /* kill command */
  231. - killCommand
  232. {
  233.     [self _sendSignal:SIGKILL];
  234.     return self;
  235. }
  236.  
  237. /* return true if running with effective user root */
  238. + (BOOL)isRunningAsRoot
  239. {
  240.     return [self _isRoot];
  241. }
  242.  
  243. // --------------------------------------------------------------------------------
  244.  
  245. /* wrapper for getpwnam() */
  246. + (struct passwd*)_getpwnam:(const char*)user
  247. {
  248.     extern void _lu_setport(port_t);
  249.     extern port_t _lookupd_port(int);
  250.     _lu_setport(_lookupd_port(0)); // may not be necessary post v3.0
  251.     return user? getpwnam(user) : getpwuid(getuid());
  252. }
  253.  
  254. /* remote: return 'root' flag */
  255. + (BOOL)_isRoot
  256. {
  257.     return ([ExecRunCommand _getpwnam:"root"]->pw_uid == geteuid())? YES : NO;
  258. }
  259.  
  260. /* set environment variable */
  261. + (int)_setenv:(char**)ep :(char*)eVal :(char*)fmt, ...
  262. {
  263.     va_list            args;
  264.     register char    *cp, *dp;
  265.     va_start(args, fmt);
  266.     vsprintf(eVal, fmt, args);
  267.     va_end(args);
  268.     for (;dp = *ep; ep++) {
  269.         for (cp = eVal; (*cp == *dp) && (*cp != '=') && *cp; cp++, dp++) continue;
  270.         if (((*cp == '=') || !*cp) && ((*dp == '=') || !*dp)) { *ep = eVal; return 0; }
  271.     }
  272.     return -1;
  273. }
  274.  
  275. // --------------------------------------------------------------------------------
  276.  
  277. /* execute a command */
  278. - (BOOL)_runCommand:(const char*)command user:(const char*)user
  279. {
  280.     inputDescriptor = [self _popen:command user:user];
  281.     if (inputDescriptor >= 0) {
  282.         DPSAddFD(inputDescriptor, gotData, self, NX_BASETHRESHOLD);
  283.         return YES;
  284.     }
  285.     return NO;
  286. }
  287.  
  288. /* send signal */
  289. - (void)_sendSignal:(int)sigval
  290. {
  291.     if (cmdChild > 0) {
  292.         killpg(cmdChild, sigval);
  293.         kill(cmdChild, sigval);
  294.     }
  295. }
  296.  
  297. /* stop command */
  298. - (void)_stopCommand
  299. {
  300.     int    error;
  301.     DPSRemoveFD(inputDescriptor);
  302.     error = [self _pclose];
  303.     close(inputDescriptor);
  304.     [self commandDidCompleteWithError:error];
  305. }
  306.  
  307. /* open pipe to shell and execute command */
  308. - (int)_popen:(const char*)cmd user:(const char*)user
  309. {
  310.     int            inputP[2], hisOutput, myInput;
  311.     const char    **locEnv = environ;
  312.  
  313.     /* only "root" can specify a user */
  314.     if (user && ![[self class] _isRoot]) return -1;
  315.     
  316.     /* "root" user commands must be preceeded with "{root}..." */
  317.     if (user && !strcmp(user,"root")) {
  318.         const char *match = "{root}";
  319.         int matchLen = strlen(match);
  320.         if (strncmp(cmd,match,matchLen)) return -1;
  321.         cmd += matchLen;
  322.     }
  323.  
  324.     /* open pipe */
  325.     pipe(inputP);
  326.     myInput = inputP[0];
  327.     hisOutput = inputP[1];
  328.  
  329.     /* fork and execute shell */
  330.     if ((cmdChild = vfork()) == 0) {
  331.         int i;
  332.         char **env;
  333.         setpgrp(0, getpid());
  334.  
  335.         /* set up pipe handles */
  336.         close(myInput);
  337.         if (hisOutput != 1) dup2(hisOutput, 1);
  338.         if (hisOutput != 2) dup2(hisOutput, 2);
  339.         if ((hisOutput != 1) && (hisOutput != 2)) close(hisOutput);
  340.  
  341.         /* make local copy of environment table */
  342.         for (i = 0; locEnv[i]; i++);
  343.         env = (char**)alloca(sizeof(char*) * (i + 2 + 1)); // allocate on stack
  344.         memset(env, 0,       sizeof(char*) * (i + 2 + 1));
  345.         memcpy(env, locEnv,  sizeof(char*) * i);
  346.  
  347.         /* switch to user */
  348.         if ([ExecRunCommand _isRoot]) {
  349.             struct passwd *pw = [ExecRunCommand _getpwnam:user];
  350.             if ((setgid(pw->pw_gid) < 0)             ||
  351.                 (initgroups(pw->pw_name,pw->pw_gid)) ||
  352.                 (setuid(pw->pw_uid) < 0))              {
  353.                 _exit(RUNCMD_USER); // cannot switch to user
  354.             }
  355.             [ExecRunCommand _setenv:env:(char*)alloca(strlen(pw->pw_dir)+7)
  356.                 :"HOME=%s",pw->pw_dir];
  357.             [ExecRunCommand _setenv:env:(char*)alloca(strlen(pw->pw_name)+7)
  358.                 :"USER=%s",pw->pw_name];
  359.         }
  360.  
  361.         /* execute command */
  362.         // execle("/bin/csh","csh","-f","-c",cmd,(char*)nil,env);
  363.         execle("/bin/sh","sh","-c",cmd,(char*)nil,env);
  364.         _exit(RUNCMD_EXEC);
  365.  
  366.     }
  367.     
  368.     /* set io */
  369.     if (cmdChild == -1) { close(myInput); myInput = -1; } else _pid_add(cmdChild);
  370.     close(hisOutput);
  371.     
  372.     return myInput;
  373. }
  374.  
  375. /* close shell command pipe */
  376. - (int)_pclose
  377. {
  378.     union wait status;
  379.     int omask = sigblock(sigmask(SIGINT)|sigmask(SIGQUIT)|sigmask(SIGHUP));
  380.     BOOL flag = _pid_wait(cmdChild,&status);
  381.     (void)sigsetmask(omask);
  382.     if (!flag) return 0; // no children
  383.     if (status.w_status & 0xFF) return RUNCMD_STOPPED; // process was terminated
  384.     return (status.w_status >> 8) & 0xFF;
  385. }
  386.  
  387. /* filter for data piped from command shell */
  388. - (void)_gotData
  389. {
  390.     char    data[1024];
  391.     int        n, cnt = 0;
  392.  
  393.     /* read available text */
  394.     do {
  395.         if ((n = read(inputDescriptor,data,sizeof(data))) >= 0) {
  396.             cnt += n;
  397.             if (n) [self _commandOutput:data len:n];
  398.         }
  399.     } while (n == sizeof(data));
  400.  
  401.     /* stop command when done */
  402.     if (!cnt) [self _stopCommand];
  403.     
  404. }
  405.  
  406. /* fd routine for receiving data */
  407. static void gotData(int fd, void *self)
  408. {
  409.     [(ExecRunCommand*)self _gotData];
  410. }
  411.  
  412. // --------------------------------------------------------------------------------
  413. // support for remote shell server output
  414.  
  415. /* copy text to scrollView (MAIN THREAD ONLY!) */
  416. - (void)_commandOutput:(const char*)buf len:(int)len
  417. {
  418.     if (len && buf) {
  419.         if (delegate && [delegate respondsTo:@selector(commandOutput:buffer:len:)]) {
  420.             [delegate commandOutput:self buffer:buf len:len];
  421.         }
  422.     }
  423. }
  424.  
  425. /* copy text to scrollView (MAIN THREAD ONLY!) */
  426. - (oneway void)commandOutput:(const char*)buf len:(int)len
  427. {
  428.     if (len && buf) {
  429.         [self _commandOutput:buf len:len];
  430.         free((char*)buf);
  431.     }
  432. }
  433.  
  434. /* indicate that the shell has completed */
  435. - (oneway void)commandDidCompleteWithError:(int)errorCode;
  436. {
  437.     if (delegate && [delegate respondsTo:@selector(commandDidComplete:withError:)]) {
  438.         [delegate commandDidComplete:self withError:errorCode];
  439.     }
  440.     cmdChild = 0;
  441.     [self free];
  442. }
  443.  
  444. @end
  445.